package com.ease.util {
   import mx.binding.Binding;
   import mx.binding.utils.ChangeWatcher;
   import mx.collections.ArrayList;

   public class BindingSet {

      [ArrayElementType("Binding")]
      private var _bindings:Array = [];

      /**
       * Number of bindings
       */
      public function get length():int {
         return _bindings.length;
      }

      /**
       * Create a binding to update a property when the watched property is changed.
       *
       * This class is based on <code>BindingUtils.bindProperty</code> and
       * <code>BindingUtils.bindSetter</code>
       *
       * @param updateOnCreate Update site.prop on create.
       */
      public function bindProperty( site:Object, prop:String, host:Object, chain:Object,
                                    commitOnly:Boolean = false, useWeakReference:Boolean = false,
                                    updateOnCreate:Boolean = true):void {
         var binding:Binding = Binding.bindProperty(site, prop, host,
            MixedUtil.objectAsArray(chain), commitOnly, useWeakReference,
            updateOnCreate);
         _bindings.push(binding);
      }

      /**
       * Create a binding to call a setter function when the watched property is changed.
       *
       * The parameters are the same as: <code>BindingUtils.bindSetter</code> method.
       *
       * @param updateOnCreate Call setter on create.
       */
      public function bindSetter(setter:Function, host:Object, chain:Object,
                                 commitOnly:Boolean = false, useWeakReference:Boolean = false,
                                 updateOnCreate:Boolean = true):void {
         var binding:Binding = Binding.bindSetter(setter, host,
            MixedUtil.objectAsArray(chain), commitOnly, useWeakReference,
            updateOnCreate);
         _bindings.push(binding);
      }

      /**
       * Removes a binding by setter
       */
      public function unbindSetter(setter:Function, host:Object, chain:Object):void {
         var i:int = findBinding(setter, null, host, MixedUtil.objectAsArray(chain));
         unbind(i);
      }


      /**
       * Removes a binding by property
       */
      public function unbindProperty(site:Object, prop:String, host:Object, chain:Object)
         :void {
         var i:int = findBinding(site, prop, host, MixedUtil.objectAsArray(chain));
         unbind(i);
      }

      /**
       * Remove a binding by Property any host and chain.
       */
      public function unbindPropertyFromAllHosts(site:Object, prop:String):void {
         var i:int = 0;
         for(i = 0; i < _bindings.length; i++) {
            var bi:Binding = Binding(_bindings[i]);
            if (bi._site == site && bi._prop == prop) {
               unbind(i);
            }
         }
      }

      /**
       * Creates bi-directional binding between widget and data.
       * @param widget
       *    Widget, e.g. TextInput
       * @param widgetProp
       *    Widget's property to bind
       * @param data
       *    Data structure (model)
       * @param dataProp
       *    property of data to bind
       */
      public function biBind(widget:Object, wprop:String, data:Object, dprop:String):void {
         bindProperty(widget, wprop, data, dprop);
         bindProperty(data, dprop, widget, wprop);
      }

      /**
       * Unwatch and remove all the bindings.
       */
      public function unwatch():void {
         for each (var binding:Binding in _bindings) {
            binding.unwatch();
         }
         // clear the array
         _bindings.splice(0, _bindings.length);
      }

      private function findBinding(site:Object, prop:String, host:Object, chain:Array):
         int {
         var i:int = 0;
         for(i = 0; i < _bindings.length; i++) {
            var bi:Binding = Binding(_bindings[i]);
            if (bi._host == host && areEquivalentArrays(bi._chain, chain) &&
               bi._site == site && bi._prop == prop) {
               return i;
            }
         }
         return -1;
      }

      private static function areEquivalentArrays(lhs:Array, rhs:Array):Boolean {
         return MixedUtil.areEquivalentCollections(lhs, rhs,
            MixedUtil.PROPERTY_EQUIVALENCE);
      }

      private function unbind(i:int):void {
         if (i < 0) {
            return;
         }
         var bi:Binding = _bindings[i];
         _bindings.splice(i, 1);
         bi.unwatch();
      }
   }
}

import mx.binding.utils.BindingUtils;
import mx.binding.utils.ChangeWatcher;

/**
 * Provides gc friendly a property change watcher.
 *
 * This class is based on <code>BindingUtils.bindProperty</code> and
 * <code>BindingUtils.bindSetter</code>
 *
 * The bindProperty creates an activation-context which seems to make it harder for
 * objects to get collected.
 */
class Binding {
   private var _w:ChangeWatcher;
   public var _site:Object; // Site is used as Function in case
   public var _prop:String;
   public var _host:Object;
   public var _chain:Array;

   /**
    * Constructs a new <code>Binding</code> instance.
    */
   public function Binding() {
   }

   /**
    * Create a binding to update a property when the watched property is changed.
    *
    * The parameters are the same as: <code>BindingUtils.bindProperty</code> method.
    */
   public static function bindProperty( site:Object, prop:String, host:Object, chain:Array,
                                        commitOnly:Boolean = false, useWeakReference:Boolean = false,
                                        updateOnCreate:Boolean = true):Binding
   {
      var w:ChangeWatcher =
         ChangeWatcher.watch(host, chain, null, commitOnly, useWeakReference);

      if (w == null) {
         return null;
      }

      var binding:Binding = new Binding();
      binding._w = w;
      binding._site = site;
      binding._prop = prop;
      binding._host = host;
      binding._chain = chain;
      w.setHandler(binding.assign);
      if (updateOnCreate) {
         binding.assign(null);
      }
      return binding;
   }

   /**
    * Create a binding to call a setter function when the watched property is changed.
    *
    * The parameters are the same as: <code>BindingUtils.bindSetter</code> method.
    */
   public static function bindSetter(setter:Function, host:Object, chain:Array,
                                     commitOnly:Boolean = false, useWeakReference:Boolean = false,
                                     updateOnCreate:Boolean = true):Binding
   {
      var w:ChangeWatcher =
         ChangeWatcher.watch(host, chain, null, commitOnly, useWeakReference);

      if (w == null) {
         return null;
      }

      var binding:Binding = new Binding();
      binding._w = w;
      binding._site = setter;
      binding._host = host;
      binding._chain = chain;
      w.setHandler(binding.assignSetter);
      if (updateOnCreate) {
         binding.assignSetter(null);
      }
      return binding;
   }

   /**
    * Unwatch and remove all the watcher.
    */
   public function unwatch():void {
      if (_w != null) {
         _w.unwatch();
         _w.setHandler(null);
         _w = null;
      }
      _site = null;
   }

   private function assign(event:*):void {
      _site[_prop] = _w.getValue();
   }

   private function assignSetter(event:*):void {
      var setter:Function = _site as Function;
      setter(_w.getValue());
   }
}